package com.study.hotreloadsample.hotReload;

import android.app.Application;
import android.os.Build;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 作者： zjf 10/10/20 3:25 PM
 * 参考：
 * 描述：
 */
public class HotReloadUtil {

    public static void init(Application app, File dexFile) {

        //获得类加载器
        ClassLoader classLoader = app.getClassLoader();

        List<File> files = new ArrayList<>();

        if (dexFile.exists()) {
            files.add(dexFile);
        }

        File dexOptDir = app.getCacheDir();

        //版本兼容
        //7.0以上虚拟机实行了混合编译,所以会不兼容
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            try {
                NewClassLoader.inject(app, classLoader, files);
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        } else {
            //6.0以上
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                V23.install(classLoader, files, dexOptDir);
            }
            //5.0以上
            else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                V21.install(classLoader, files, dexOptDir);
            }
        }
    }

    private static final class V23 {
        static void install(ClassLoader loader, List<File> reloadDexFiles, File optimizedDirectory) {
            try {
                Field pathListField = ReflectUtil.findField(loader, "pathList");
                Object dexPathList = pathListField.get(loader);

                ArrayList<IOException> suppressedExceptions = new ArrayList<>();
                //把修复的dex转换为Element数组
                Object[] patchElements = makePathElements(dexPathList, new ArrayList<>(reloadDexFiles), optimizedDirectory, suppressedExceptions);

                //添加到dexElements数组中
                ReflectUtil.expendFieldArray(dexPathList, "dexElements", patchElements);

            } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }

        /**
         * 把dex转化为Element数组
         */
        private static Object[] makePathElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> exceptions) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            Method makePathElements = ReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class, List.class);
            return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, exceptions);
        }
    }

    private static final class V21 {
        static void install(ClassLoader loader, List<File> reloadDexFiles, File optimizedDirectory) {
            try {
                Field pathListField = ReflectUtil.findField(loader, "pathList");
                Object dexPathList = pathListField.get(loader);

                ArrayList<IOException> suppressedExceptions = new ArrayList<>();
                //把修复的dex转换为Element数组
                Object[] patchElements = makePathElements(dexPathList, new ArrayList<>(reloadDexFiles), optimizedDirectory, suppressedExceptions);

                //添加到dexElements数组中
                ReflectUtil.expendFieldArray(dexPathList, "dexElements", patchElements);

            } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }

        /**
         * 把dex转化为Element数组,不同的版本反射的方法有所不同
         */
        private static Object[] makePathElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> exceptions) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            Method makePathElements = ReflectUtil.findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class);
            return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, exceptions);
        }
    }
}
